//
//  GLFont.m
//  Tetravex
//
//  Created by Holmes Futrell on 7/18/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "GLFont.h"
#import "common.h"
#import <math.h>

#import "platform.h"

@interface GLFont (internalMethods)
-(void)buildAtlas;
@end

@implementation GLFont

@synthesize font=fontRef;

GLuint vertexAttributeLocation;
GLuint texcoordsAttributeLocation;

-(id)initWithFontName:(NSString *)name
				 size:(int)_size
				color:(GLubyte *)_color
			   stroke:(float)_stroke
		  strokeColor:(GLubyte *)_strokeColor;
{
	
	
	self = [super init];
	
	size = _size;
	stroke = _stroke;
	memcpy(color, _color, 4 * sizeof(GLubyte));
	
	if (_strokeColor) {
		memcpy(strokeColor, _strokeColor, 4 * sizeof(GLubyte));
	}
		
	fontRef = CTFontCreateWithName((CFStringRef)name, _size, NULL);
	if (fontRef == NULL) {
		NSLog(@"Error loading font (no font named %@)", name);
		return nil;
	}
	
	[self buildAtlas];
	
	return self;
	
}


-(id)initWithFontPath:(NSString *)path 
				 size:(int)_size
				color:(GLubyte *)_color
			   stroke:(float)_stroke
		  strokeColor:(GLubyte *)_strokeColor;
{

	self = [super init];
		
	size = _size;
	stroke = _stroke;
	memcpy(color, _color, 4 * sizeof(GLubyte));
	
	if (_strokeColor) {
		memcpy(strokeColor, _strokeColor, 4 * sizeof(GLubyte));
	}
	
	NSURL *url = [[NSURL alloc] initFileURLWithPath: path];
	CGDataProviderRef provider = CGDataProviderCreateWithURL((CFURLRef)url);
	
	if (provider == NULL) {
		NSLog(@"Error opening font file");
		return nil;
	}
	
	CGFontRef cgFontRef = CGFontCreateWithDataProvider(provider);
	
	fontRef = CTFontCreateWithGraphicsFont(cgFontRef, _size, NULL, NULL);
	
	CFRelease(cgFontRef);
	
	if (fontRef == NULL) {
		NSLog(@"Error loading font");
		return nil;
	}
	
	CFRelease(provider);
	[url release];
	
	[self buildAtlas];
	
	return self;
	
}

-(void)dealloc {

	CFRelease(fontRef);
	fontRef = nil;
	free(vertices);
	free(texCoords);
	free(advances);
	[atlasSprite release];
	atlasSprite = nil;
	[super dealloc];
	
}

-(float)glyphToPixel {
	return 1.0f / CTFontGetUnitsPerEm(fontRef) * size;
}


-(int)charToGlyph:(unichar)c {
	
	CGGlyph glyph;
	
	CTFontGetGlyphsForCharacters( fontRef, &c, &glyph, 1);

	
	return glyph;
}

-(CGSize)sizeForString:(NSString *)string kerning:(float)kerning {
	
	CGSize stringSize = CGSizeZero;
	
	int i;
	for (i=0; i<[string length]; i++) {
		unichar u = [string characterAtIndex: i];
		//unsigned int glyph = [self charToGlyph: u];
		stringSize.width += advances[u - 32];
		stringSize.width += kerning;
	}
	stringSize.height = ascent + stroke;
		
	return stringSize;
}

-(CGSize)sizeForString:(NSString *)string {
	return [self sizeForString: string kerning: 0.0];	
}

-(void)drawString:(NSString *)string kerning:(float)kerning {
	
#if 0 // OpenGL ES 1.0 code
	glPushMatrix();
	
	glBindTexture(GL_TEXTURE_2D, atlasSprite.descriptor);
	
	glVertexPointer(2, GL_SHORT, 0, vertices);
	glEnableClientState(GL_VERTEX_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	float translation = 0.0f;
	
	int len = [string length];
	
	unichar buffer[len+1];
	
	[string getCharacters: buffer];
	
	int i;
	for (i=0; i<len; i++) {
		
		glPushMatrix();
		
		glTranslatef( ceil(translation), 0, 0);
		
		unichar u = buffer[i];
		unsigned int index = u - 32;
		GLushort indices[4];
		indices[0] = 4 * index + 0;
		indices[1] = 4 * index + 1;
		indices[2] = 4 * index + 2;
		indices[3] = 4 * index + 3;

		translation += advances[index] + kerning;
		
		glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices);
		/* rounding here causes text blurring */
		
		glPopMatrix();
		
	}
	
	glPopMatrix();
#endif

	
	float translation = 0.0f;
	int len = [string length];
	unichar buffer[len+1];
	[string getCharacters: buffer];
	
	GLfloat  pos[4 * len * 2];
	GLfloat  uv[4 * len * 2];

	GLushort indices[6 * len];
	
	int i;
	for (i=0; i<len; i++) {

		int charTranslate = ceil(translation);
		
		unichar u = buffer[i];
		unsigned int index = u - 32;
		
		int tl = 4 * index + 0;
		int tr = 4 * index + 1;
		int bl = 4 * index + 2;
		int br = 4 * index + 3;
		
		indices[6 * i + 0] = 4 * i + 0; 
		indices[6 * i + 1] = 4 * i + 2;
		indices[6 * i + 2] = 4 * i + 3;
		indices[6 * i + 3] = 4 * i + 3;
		indices[6 * i + 4] = 4 * i + 1;
		indices[6 * i + 5] = 4 * i + 0;

		pos[8 * i + 0 ] = vertices[2*tl+0] + charTranslate;
		pos[8 * i + 1 ] = vertices[2*tl+1];
		pos[8 * i + 2 ] = vertices[2*tr+0] + charTranslate;
		pos[8 * i + 3 ] = vertices[2*tr+1];
		pos[8 * i + 4 ] = vertices[2*bl+0] + charTranslate;
		pos[8 * i + 5 ] = vertices[2*bl+1];		
		pos[8 * i + 6 ] = vertices[2*br+0] + charTranslate;
		pos[8 * i + 7 ] = vertices[2*br+1];
		
		uv[8 * i + 0 ] = texCoords[2*tl+0];
		uv[8 * i + 1 ] = texCoords[2*tl+1];
		uv[8 * i + 2 ] = texCoords[2*tr+0];
		uv[8 * i + 3 ] = texCoords[2*tr+1];
		uv[8 * i + 4 ] = texCoords[2*bl+0];
		uv[8 * i + 5 ] = texCoords[2*bl+1];		
		uv[8 * i + 6 ] = texCoords[2*br+0];
		uv[8 * i + 7 ] = texCoords[2*br+1];		
		
		translation += advances[index] + kerning;
		
		/* rounding here causes text blurring */
				
	}
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, atlasSprite.descriptor);
		
	rb_check_error_simple();
	
	glEnableVertexAttribArray(vertexAttributeLocation);
	
	rb_check_error_simple();
	
	glVertexAttribPointer(vertexAttributeLocation, 2, GL_FLOAT, 0, 0, pos);
	
	rb_check_error_simple();
	
	glEnableVertexAttribArray(texcoordsAttributeLocation);
	glVertexAttribPointer(texcoordsAttributeLocation, 2, GL_FLOAT, 0, 0, uv);
	
	rb_check_error_simple();
	
	glDrawElements(GL_TRIANGLES, 6 * len, GL_UNSIGNED_SHORT, indices);
	
	rb_check_error_simple();
	
	glDisableVertexAttribArray(vertexAttributeLocation);
	glDisableVertexAttribArray(texcoordsAttributeLocation);

	
}

+(void)mapVerticesToAttributeLocation:(GLuint)location {
	vertexAttributeLocation = location;
}

+(void)mapTexCoordsToAttributeLocation:(GLuint)location {
	texcoordsAttributeLocation = location;
}


-(void)drawString:(NSString *)string {
	[self drawString: string kerning: 0.0f];
}

-(void)buildAtlas {

	CGColorSpaceRef colorSpace;
	CGContextRef context;
	GLubyte *data;	
	int i;

	CGFontRef cgFontRef = CTFontCopyGraphicsFont( fontRef, NULL );
	
#define ASCII_ONLY 1
	
	CGSize atlasSize = CGSizeMake( 128, 0 );
	
	float glyphToPixel = [self glyphToPixel];
	
	//size_t numGlyphs = CGFontGetNumberOfGlyphs(cgFontRef);
	
#if ASCII_ONLY == 1
	size_t numGlyphs = 127 - 32;
#endif
	
	CGGlyph *glyphs = malloc(sizeof(CGGlyph) * numGlyphs);
	CGRect *bboxes	= malloc(sizeof(CGRect) * numGlyphs);
	
	CGSize *atlasSizes		= malloc(sizeof(CGSize) * numGlyphs);
	CGPoint *atlasOrigins	= malloc(sizeof(CGPoint) * numGlyphs);
	
	UniChar characters[numGlyphs];
	for (i=0; i<numGlyphs; i++) {
		characters[i] = i + 32;
	}
	
	CTFontGetGlyphsForCharacters( fontRef, characters, glyphs, numGlyphs);
	
	
	/* get advances */
	int *advances_i = malloc(sizeof(int) * numGlyphs);
	advances = malloc(sizeof(GLfloat) * numGlyphs);
	CGFontGetGlyphAdvances(cgFontRef, glyphs, numGlyphs, advances_i);
	for (i=0; i<numGlyphs; i++) {
		advances[i] = advances_i[i] * glyphToPixel;
	}
	free(advances_i);
	/* end get advances */
	
	/* get bounding boxes for each glyph */
	CGFontGetGlyphBBoxes(cgFontRef, glyphs, numGlyphs, bboxes);
	for (i=0; i<numGlyphs; i++) {
		bboxes[i].size.width	*= glyphToPixel;
		bboxes[i].size.height	*= glyphToPixel;
		bboxes[i].origin.x		*= glyphToPixel;
		bboxes[i].origin.y		*= glyphToPixel;
	}
	
	descent = CGFontGetDescent(cgFontRef) * glyphToPixel;
	ascent  = CGFontGetAscent(cgFontRef) * glyphToPixel;
	CGPoint currentPosition = CGPointMake(0, -descent);
		
	for (i=0; i<numGlyphs; i++) {
		
		/* do we need to go to the next line in the atlas ? */
		if (currentPosition.x + advances[i] >= atlasSize.width) {
			currentPosition.x = 0;
			currentPosition.y += ascent - descent + 3 + 3 * stroke;
		}
		
		
		atlasOrigins[i].x		= ceil(currentPosition.x);
		atlasOrigins[i].y		= ceil(currentPosition.y);
		atlasSizes[i].width		= ceil(advances[i] + 2 * stroke);
		atlasSizes[i].height	= ceil(-descent + ascent + 2 * stroke);
		
#define DEBUG_FONT_BOXES 0
#if DEBUG_FONT_BOXES
		NSLog(@"glyph %d width = %f, height = %f", i, atlasSizes[i].width, atlasSizes[i].height);
#endif		
		
		currentPosition.x += atlasSizes[i].width + 2;
		
	}
	
	atlasSize.height = powerOfTwo(currentPosition.y + ascent + stroke); 
	
	/* create the context, draw the atlas */
	colorSpace = CGColorSpaceCreateDeviceRGB();
	data = calloc(atlasSize.width * atlasSize.height, 4);
	context = CGBitmapContextCreate(data, atlasSize.width, atlasSize.height, 8, atlasSize.width * 4, colorSpace, kCGImageAlphaNoneSkipLast);
	

	float inv255 = 1.0f / 255.0f;
	
	
	CGContextSetFont(context, cgFontRef);
	CGContextSetFontSize(context, size);
	CGContextSetTextPosition(context, 0, 0);
	CGContextSetInterpolationQuality(context,  kCGInterpolationDefault);
	
	if (stroke > 0) {
		
		CGContextSetTextPosition(context, 0, stroke);
		CGContextSetLineWidth(context, 2.0 * stroke);
		CGContextSetTextDrawingMode(context, kCGTextFill);
		CGContextSetRGBFillColor(context, inv255 * strokeColor[0], inv255 * strokeColor[1], inv255 * strokeColor[2], inv255 * strokeColor[3]);		
		CGContextShowGlyphsAtPositions(context, glyphs, atlasOrigins, numGlyphs);		
		
		CGContextSetTextPosition(context, 0, 0);
		CGContextSetRGBFillColor(context, inv255 * color[0], inv255 * color[1], inv255 * color[2],  inv255 * color[3]);
		CGContextSetTextDrawingMode(context, kCGTextFill);
		CGContextShowGlyphsAtPositions(context, glyphs, atlasOrigins, numGlyphs);

	}
	else {
		CGContextSetRGBFillColor(context, inv255 * color[0], inv255 * color[1], inv255 * color[2],  inv255 * color[3]);
		CGContextSetTextDrawingMode(context, kCGTextFill);
		CGContextShowGlyphsAtPositions(context, glyphs, atlasOrigins, numGlyphs);
	}
		
	CGContextFlush(context);
	
	/* create the texture */
	atlasSprite = [[GLSprite alloc] initWithSize: atlasSize flags: 0];
	
	glBindTexture(GL_TEXTURE_2D, atlasSprite.descriptor);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLuint)atlasSize.width, (GLuint)atlasSize.height, GL_RGBA, GL_UNSIGNED_BYTE, data);

	
	/* create the triangles and indices */
	int numVertices = 4 * numGlyphs;
	vertices	= (GLshort *)malloc( 2 * numVertices * sizeof(GLshort));
	texCoords	= (GLfloat *)malloc( 2 * numVertices * sizeof(GLfloat));
	
	for (i=0; i<numGlyphs; i++) {
		vertices[8 * i + 0] = 0.0;
		vertices[8 * i + 1] = 0.0;
		vertices[8 * i + 2] = atlasSizes[i].width;
		vertices[8 * i + 3] = 0.0;
		vertices[8 * i + 4] = 0.0;
		vertices[8 * i + 5] = atlasSizes[i].height;
		vertices[8 * i + 6] = atlasSizes[i].width;
		vertices[8 * i + 7] = atlasSizes[i].height;
	}
	
	for (i=0; i<numGlyphs; i++) {
		texCoords[8 * i + 0] = atlasOrigins[i].x - stroke;
		texCoords[8 * i + 1] = atlasOrigins[i].y + atlasSizes[i].height + descent;
		texCoords[8 * i + 2] = atlasOrigins[i].x + atlasSizes[i].width - stroke;
		texCoords[8 * i + 3] = atlasOrigins[i].y + atlasSizes[i].height + descent;
		texCoords[8 * i + 4] = atlasOrigins[i].x  - stroke;
		texCoords[8 * i + 5] = atlasOrigins[i].y + descent;
		texCoords[8 * i + 6] = atlasOrigins[i].x + atlasSizes[i].width - stroke;
		texCoords[8 * i + 7] = atlasOrigins[i].y + descent;
	}
	
	for (i=0; i< numVertices; i++) {
		texCoords[2 * i + 0] /= atlasSize.width;
		texCoords[2 * i + 1] /= atlasSize.height;
		texCoords[2 * i + 1] = 1.0 - texCoords[2 * i + 1];
	}
	
	CFRelease(cgFontRef);
	
	/* free everything */
	free(glyphs);
	free(bboxes);
	free(atlasSizes);
	free(atlasOrigins);
	free(data);
	CGColorSpaceRelease(colorSpace);
	CGContextRelease(context);

}

-(void)drawDebug {

#if 0 // must be rewritten for OGLES 2.0
	
	[atlasSprite draw];
	
	GLfloat coords[8];
	coords[0] = -1.0f; coords[1] = -1.0f;
	coords[2] = atlasSprite.width+1.0f; coords[3] = -1.0f;
	coords[4] = atlasSprite.width+1.0f; coords[5] = atlasSprite.height;
	coords[6] = -1.0f; coords[7] = atlasSprite.height+1.0f;

	glVertexPointer(2, GL_FLOAT, 0, coords);
	glEnableClientState(GL_VERTEX_ARRAY);

	glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
	
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);
	
	glDrawArrays(GL_LINE_LOOP, 0, 4);
	
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnable(GL_TEXTURE_2D);
	
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

#endif
	
}

@end
